package com.boilermaker.lotterysystem.service.impl;

import com.boilermaker.lotterysystem.common.errorcode.ServiceErrorCodeConstants;
import com.boilermaker.lotterysystem.common.exception.ServiceException;
import com.boilermaker.lotterysystem.common.utils.JacksonUtil;
import com.boilermaker.lotterysystem.common.utils.RedisUtil;
import com.boilermaker.lotterysystem.controller.param.DrawPrizeParam;
import com.boilermaker.lotterysystem.controller.param.ShowWinningRecordsParam;
import com.boilermaker.lotterysystem.dao.dataobject.*;
import com.boilermaker.lotterysystem.dao.mapper.*;
import com.boilermaker.lotterysystem.service.ActivityService;
import com.boilermaker.lotterysystem.service.DrawPrizeService;
import com.boilermaker.lotterysystem.service.dto.ActivityDetailDTO;
import com.boilermaker.lotterysystem.service.dto.WinningRecordDTO;
import com.boilermaker.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.boilermaker.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.boilermaker.lotterysystem.service.enums.ActivityStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

import static com.boilermaker.lotterysystem.common.config.LotteryRabbitMQConfig.*;

@Service
@Slf4j
public class DrawPrizeServiceImpl implements DrawPrizeService {

    private static final String REDIS_WINNER_RECORD_PREFIX = "winner_record_";

    private static final Long REDIS_WINNING_RECORDS_TIMEOUT = 60 * 60 * 24 * 3L; // 中奖者记录缓存时间 2 天

    @Autowired
    private ActivityMapper activityMapper;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ActivityService activityService;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PrizeMapper prizeMapper;

    @Autowired
    private WinningRecordMapper winningRecordMapper;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 异步保存中奖信息
     */
    @Override
    public void drawPrize(DrawPrizeParam param) {

        Map<String, String> message = new HashMap<>();
        message.put("messageId", String.valueOf(UUID.randomUUID()));
        message.put("messageBody", JacksonUtil.writeValueAsString(param));

        rabbitTemplate.convertAndSend(LOTTERY_EXCHANGE, LOTTERY_ROUTING_KEY, message);

        log.info("向 Broker 发布消息：messageId = {}",
                JacksonUtil.writeValueAsString(message.get("messageId")));
    }

    /**
     * 校验中奖信息有效性
     */
    @Override
    public void checkDrawPrizeParam(DrawPrizeParam param) {

        if (!validateByCache(param)) {
            log.warn("中奖信息缓存校验未通过，尝试在数据库中校验");

            if (!validateByDatabase(param)) {
                log.warn("中奖信息数据库校验未通过，param={}", JacksonUtil.writeValueAsString(param));
                throw new ServiceException(ServiceErrorCodeConstants.CHECK_DRAW_PRIZE_ERROR);
            }
        }
    }

    /**
     * 保存中奖记录（缓存 + 数据库）<P>
     * 结构：活动(奖品1(中奖人1, 中奖人2), 奖品2(中奖人3)) <P>
     * DrawPrizeParam -> 奖品维度 <P>
     * List<WinningRecordDO> -> 奖品维度 <P>
     * WinningRecordDO -> 中奖者维度 <P>
     * 数据库记录 -> 中奖者维度（WinningRecord）<P>
     * 缓存 -> 奖品维度 + 活动维度
     *
     * @return 返回一份奖品维度的中奖记录，供抽奖后续业务逻辑使用
     */
    @Override
    public List<WinningRecordDO> saveWinningRecords(DrawPrizeParam param) {

        Long activityId = param.getActivityId();
        Long prizeId = param.getPrizeId();
        List<Long> winnerIdList = param.getWinnerList()
                .stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList());

        ActivityDO activityDO = activityMapper.selectById(activityId);
        PrizeDO prizeDO = prizeMapper.selectById(prizeId);
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByActivityIdAndPrizeId(activityId, prizeId);
        List<UserDO> userDOList = userMapper.batchSelectByIds(winnerIdList);

        List<WinningRecordDO> winningRecordDOList = userDOList.stream()
                .map(userDO -> {
                    WinningRecordDO winningRecordDO = new WinningRecordDO();

                    winningRecordDO.setActivityId(activityId);
                    winningRecordDO.setActivityName(activityDO.getActivityName());
                    winningRecordDO.setPrizeId(prizeId);
                    winningRecordDO.setPrizeName(prizeDO.getName());
                    winningRecordDO.setPrizeTier(activityPrizeDO.getPrizeTiers());
                    winningRecordDO.setWinnerId(userDO.getId());
                    winningRecordDO.setWinnerName(userDO.getUserName());
                    winningRecordDO.setWinnerEmail(userDO.getEmail());
                    winningRecordDO.setWinnerPhoneNumber(userDO.getPhoneNumber());
                    winningRecordDO.setWinningTime(param.getWinningTime());

                    return winningRecordDO;
                }).collect(Collectors.toList());

        // 落库
        winningRecordMapper.batchInsert(winningRecordDOList);

        // 奖品维度缓存
        cachePrizeWinningRecords(winningRecordDOList);

        // 活动维度缓存（仅当活动已结束时）
        if (activityDO.getStatus().equalsIgnoreCase(ActivityStatusEnum.COMPLETED.name())) {
            cacheActivityWinningRecords(activityId);
        }

        return winningRecordDOList;
    }

    /**
     * 获取中奖者名单
     */
    @Override
    public List<WinningRecordDTO> getWinningRecords(ShowWinningRecordsParam param) {

        Long activityId = param.getActivityId();
        Long prizeId = param.getPrizeId();

        if (activityId == null) {
            log.warn("获取中奖者名单失败，因为活动 ID 为空");
            return List.of();
        }

        // 查缓存
        List<WinningRecordDO> winningRecordDOList =
                getWinningRecordsFromCacheOnly(activityId, prizeId);

        // 若缓存命中
        if (!CollectionUtils.isEmpty(winningRecordDOList)) {
            convertWinningRecordsDoToDto(winningRecordDOList);
        }

        // 若未命中，则查库，再重新缓存
        if (prizeId == null) {
            cacheActivityWinningRecords(activityId);
        } else {
            winningRecordDOList =
                    winningRecordMapper.selectByActivityIdAndPrizeId(activityId, prizeId);

            if (CollectionUtils.isEmpty(winningRecordDOList)) {
                log.warn("重新缓存失败，因为数据库不存在该活动或奖品，activityId={}，prizeId={}",
                        activityId, prizeId);
                return List.of();
            }

            cachePrizeWinningRecords(winningRecordDOList);
        }

        return convertWinningRecordsDoToDto(winningRecordDOList);
    }

    /**
     * DO -> DTO
     */
    private List<WinningRecordDTO> convertWinningRecordsDoToDto(List<WinningRecordDO> winningRecordDOList) {

        return winningRecordDOList.stream()
                .map(winningRecordDO -> {
                    WinningRecordDTO winningRecordDTO = new WinningRecordDTO();

                    winningRecordDTO.setWinnerId(winningRecordDO.getWinnerId());
                    winningRecordDTO.setWinnerName(winningRecordDO.getWinnerName());
                    winningRecordDTO.setPrizeName(winningRecordDO.getPrizeName());
                    winningRecordDTO.setPrizeTier(ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()));
                    winningRecordDTO.setWinningTime(winningRecordDO.getWinningTime());

                    return winningRecordDTO;
                }).collect(Collectors.toList());
    }

    /**
     * 活动维度缓存
     * key:ACTIVITY_activityId, val:activityWinningRecords(JSON)
     * （该方法会在内部处理异常，不会抛出而导致事务回滚）
     */
    private void cacheActivityWinningRecords(Long activityId) {

        if (activityId == null) {
            log.warn("缓存失败，因为参数为空");
            return;
        }

        // 从活动维度获取中奖记录
        List<WinningRecordDO> activityWinningRecords =
                winningRecordMapper.selectByActivityId(activityId);

        if (CollectionUtils.isEmpty(activityWinningRecords)) {
            log.warn("缓存失败，因为数据库不存在该活动或奖品，activityId={}", activityId);
        }

        String key = REDIS_WINNER_RECORD_PREFIX + activityId;
        String val = JacksonUtil.writeValueAsString(activityWinningRecords);
        try {
            redisUtil.set(key, val, REDIS_WINNING_RECORDS_TIMEOUT);
        } catch (Exception e) {
            log.error("缓存失败, key={}, val={}", key, val);
        }
    }

    /**
     * 奖品维度缓存
     * key:ACTIVITY_activityId_prizeId, val:prizeWinningRecords(JSON)
     * （该方法会在内部处理异常，不会抛出而导致事务回滚。）
     */
    private void cachePrizeWinningRecords(List<WinningRecordDO> prizeWinningRecords) {

        if (CollectionUtils.isEmpty(prizeWinningRecords)) {
            log.warn("缓存失败，因为参数为空");
            return;
        }

        WinningRecordDO temp = prizeWinningRecords.get(0);

        String key = REDIS_WINNER_RECORD_PREFIX + temp.getActivityId() + "_" + temp.getPrizeId();
        String val = JacksonUtil.writeValueAsString(prizeWinningRecords);
        try {
            redisUtil.set(key, val, REDIS_WINNING_RECORDS_TIMEOUT);
        } catch (Exception e) {
            log.error("缓存失败, key={}, val={}", key, val);
        }
    }

    /**
     *
     */
    private List<WinningRecordDO> getWinningRecordsFromCacheOnly(Long activityId, Long prizeId) {

        String key;
        if (prizeId == null) {
            key = REDIS_WINNER_RECORD_PREFIX + activityId;
        } else {
            key = REDIS_WINNER_RECORD_PREFIX + activityId + "_" + prizeId;
        }

        try {
            String val = redisUtil.get(key);

            if (!StringUtils.hasText(val)) {
                log.warn("获取缓存数据失败，因为活动信息不存在，key = {}", key);
                return List.of();
            }
            return JacksonUtil.readListValue(val, WinningRecordDO.class);
        } catch (Exception e) {
            log.error("获取缓存数据失败，key = {}", key, e);
            return List.of();
        }
    }

    /**
     * 在缓存中校验
     */
    private boolean validateByCache(DrawPrizeParam param) {

        Long activityId = param.getActivityId();
        Long prizeId = param.getPrizeId();

        ActivityDetailDTO activityDetail =
                activityService.getActivityDetailFromCacheOnly(activityId);

        if (activityDetail == null) {
            log.warn("中奖信息缓存校验未通过，因为找不到该活动，activityId={}", activityId);
            return false;
        }

        if (!activityDetail.valid()) {
            log.warn("中奖信息缓存校验未通过，因为活动已结束，activityId={}", activityId);
            return false;
        }

        // 奖品是否存在
        List<ActivityDetailDTO.ActivityPrizeDTO> prizeDTOList = activityDetail.getPrizeDTOList();

        ActivityDetailDTO.ActivityPrizeDTO targetPrize = prizeDTOList.stream()
                .filter(prizeDTO -> prizeId.equals(prizeDTO.getPrizeId()))
                .findFirst()
                .orElse(null);

        if (targetPrize == null) {
            log.warn("中奖信息缓存校验未通过，因为找不到该奖品，prizeId={}", prizeId);
            return false;
        }

        if (!targetPrize.valid()) {
            log.warn("中奖信息缓存校验未通过，因为该奖品已失效，prizeId={}", prizeId);
            return false;
        }

        Long prizeAmount = targetPrize.getPrizeAmount();
        int winnerAmount = param.getWinnerList().size();

        if (prizeAmount != winnerAmount) {
            log.warn("中奖信息缓存校验未通过，因为奖品数量与中奖者数量不一致，prizeId={}，" +
                    "prizeAmount={}，winnerAmount={}", prizeId, prizeAmount, winnerAmount);
            return false;
        }

        // 中奖者是否均为活动关联的人员
        List<ActivityDetailDTO.ActivityUserDTO> userDTOList = activityDetail.getUserDTOList();

        Set<Long> userIds = userDTOList.stream()
                .map(ActivityDetailDTO.ActivityUserDTO::getUserId)
                .collect(Collectors.toSet());

        boolean allUsersExist = param.getWinnerList().stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .allMatch(userIds::contains);

        if (!allUsersExist) {
            log.warn("中奖信息缓存校验未通过，因为中奖者不均为活动关联的人员");
            return false;
        }

        return true;
    }

    /**
     * 在数据库中校验
     */
    private boolean validateByDatabase(DrawPrizeParam param) {

        Long activityId = param.getActivityId();
        Long prizeId = param.getPrizeId();

        ActivityDO activityDO = activityMapper.selectById(activityId);

        if (activityDO == null) {
            log.warn("中奖信息数据库校验未通过，因为找不到该活动，activityId={}", activityId);
            return false;
        }

        if (activityDO.getStatus().equalsIgnoreCase(ActivityStatusEnum.COMPLETED.name())) {
            log.warn("中奖信息数据库校验未通过，因为活动已结束，activityId={}", activityId);
            return false;
        }

        ActivityPrizeDO activityPrizeDO =
                activityPrizeMapper.selectByActivityIdAndPrizeId(activityId, prizeId);

        if (activityPrizeDO == null) {
            log.warn("中奖信息数据库校验未通过，因为找不到该奖品，prizeId={}", prizeId);
            return false;
        }

        if (activityPrizeDO.getStatus().equals(ActivityPrizeStatusEnum.UNAVAILABLE.name())) {
            log.warn("中奖信息数据库校验未通过，因为该奖品已失效，prizeId={}", prizeId);
            return false;
        }

        Long prizeAmount = activityPrizeDO.getPrizeAmount();
        int winnerAmount = param.getWinnerList().size();

        if (prizeAmount != winnerAmount) {
            log.warn("中奖信息数据库校验未通过，因为奖品数量与中奖者数量不一致，prizeId={}，" +
                    "prizeAmount={}，winnerAmount={}", prizeId, prizeAmount, winnerAmount);
            return false;
        }

        return true;
    }
}
